/*!
 * # Semantic UI - Popup
 * http://github.com/semantic-org/semantic-ui/
 *
 *
 * Released under the MIT license
 * http://opensource.org/licenses/MIT
 *
 */

(function($, window, document, undefined) {
  "use strict";

  window =
    typeof window != "undefined" && window.Math == Math
      ? window
      : typeof self != "undefined" && self.Math == Math
        ? self
        : Function("return this")();

  $.fn.popup = function(parameters) {
    var $allModules = $(this),
      $document = $(document),
      $window = $(window),
      $body = $("body"),
      moduleSelector = $allModules.selector || "",
      hasTouch = true,
      time = new Date().getTime(),
      performance = [],
      query = arguments[0],
      methodInvoked = typeof query == "string",
      queryArguments = [].slice.call(arguments, 1),
      returnedValue;
    $allModules.each(function() {
      var settings = $.isPlainObject(parameters)
          ? $.extend(true, {}, $.fn.popup.settings, parameters)
          : $.extend({}, $.fn.popup.settings),
        selector = settings.selector,
        className = settings.className,
        error = settings.error,
        metadata = settings.metadata,
        namespace = settings.namespace,
        eventNamespace = "." + settings.namespace,
        moduleNamespace = "module-" + namespace,
        $module = $(this),
        $context = $(settings.context),
        $scrollContext = $(settings.scrollContext),
        $boundary = $(settings.boundary),
        $target = settings.target ? $(settings.target) : $module,
        $popup,
        $offsetParent,
        searchDepth = 0,
        triedPositions = false,
        openedWithTouch = false,
        element = this,
        instance = $module.data(moduleNamespace),
        documentObserver,
        elementNamespace,
        id,
        module;

      module = {
        // binds events
        initialize: function() {
          module.debug("Initializing", $module);
          module.createID();
          module.bind.events();
          if (!module.exists() && settings.preserve) {
            module.create();
          }
          if (settings.observeChanges) {
            module.observeChanges();
          }
          module.instantiate();
        },

        instantiate: function() {
          module.verbose("Storing instance", module);
          instance = module;
          $module.data(moduleNamespace, instance);
        },

        observeChanges: function() {
          if ("MutationObserver" in window) {
            documentObserver = new MutationObserver(
              module.event.documentChanged
            );
            documentObserver.observe(document, {
              childList: true,
              subtree: true
            });
            module.debug("Setting up mutation observer", documentObserver);
          }
        },

        refresh: function() {
          if (settings.popup) {
            $popup = $(settings.popup).eq(0);
          } else {
            if (settings.inline) {
              $popup = $target.nextAll(selector.popup).eq(0);
              settings.popup = $popup;
            }
          }
          if (settings.popup) {
            $popup.addClass(className.loading);
            $offsetParent = module.get.offsetParent();
            $popup.removeClass(className.loading);
            if (
              settings.movePopup &&
              module.has.popup() &&
              module.get.offsetParent($popup)[0] !== $offsetParent[0]
            ) {
              module.debug("Moving popup to the same offset parent as target");
              $popup.detach().appendTo($offsetParent);
            }
          } else {
            $offsetParent = settings.inline
              ? module.get.offsetParent($target)
              : module.has.popup()
                ? module.get.offsetParent($popup)
                : $body;
          }
          if ($offsetParent.is("html") && $offsetParent[0] !== $body[0]) {
            module.debug("Setting page as offset parent");
            $offsetParent = $body;
          }
          if (module.get.variation()) {
            module.set.variation();
          }
        },

        reposition: function() {
          module.refresh();
          module.set.position();
        },

        destroy: function() {
          module.debug("Destroying previous module");
          if (documentObserver) {
            documentObserver.disconnect();
          }
          // remove element only if was created dynamically
          if ($popup && !settings.preserve) {
            module.removePopup();
          }
          // clear all timeouts
          clearTimeout(module.hideTimer);
          clearTimeout(module.showTimer);
          // remove events
          module.unbind.close();
          module.unbind.events();
          $module.removeData(moduleNamespace);
        },

        event: {
          start: function(event) {
            var delay = $.isPlainObject(settings.delay)
              ? settings.delay.show
              : settings.delay;
            clearTimeout(module.hideTimer);
            if (!openedWithTouch) {
              module.showTimer = setTimeout(module.show, delay);
            }
          },
          end: function() {
            var delay = $.isPlainObject(settings.delay)
              ? settings.delay.hide
              : settings.delay;
            clearTimeout(module.showTimer);
            module.hideTimer = setTimeout(module.hide, delay);
          },
          touchstart: function(event) {
            openedWithTouch = true;
            module.show();
          },
          resize: function() {
            if (module.is.visible()) {
              module.set.position();
            }
          },
          documentChanged: function(mutations) {
            [].forEach.call(mutations, function(mutation) {
              if (mutation.removedNodes) {
                [].forEach.call(mutation.removedNodes, function(node) {
                  if (node == element || $(node).find(element).length > 0) {
                    module.debug(
                      "Element removed from DOM, tearing down events"
                    );
                    module.destroy();
                  }
                });
              }
            });
          },
          hideGracefully: function(event) {
            var $target = $(event.target),
              isInDOM = $.contains(document.documentElement, event.target),
              inPopup = $target.closest(selector.popup).length > 0;
            // don't close on clicks inside popup
            if (event && !inPopup && isInDOM) {
              module.debug("Click occurred outside popup hiding popup");
              module.hide();
            } else {
              module.debug("Click was inside popup, keeping popup open");
            }
          }
        },

        // generates popup html from metadata
        create: function() {
          var html = module.get.html(),
            title = module.get.title(),
            content = module.get.content();

          if (html || content || title) {
            module.debug("Creating pop-up html");
            if (!html) {
              html = settings.templates.popup({
                title: title,
                content: content
              });
            }
            $popup = $("<div/>")
              .addClass(className.popup)
              .data(metadata.activator, $module)
              .html(html);
            if (settings.inline) {
              module.verbose("Inserting popup element inline", $popup);
              $popup.insertAfter($module);
            } else {
              module.verbose("Appending popup element to body", $popup);
              $popup.appendTo($context);
            }
            module.refresh();
            module.set.variation();

            if (settings.hoverable) {
              module.bind.popup();
            }
            settings.onCreate.call($popup, element);
          } else if ($target.next(selector.popup).length !== 0) {
            module.verbose("Pre-existing popup found");
            settings.inline = true;
            settings.popup = $target
              .next(selector.popup)
              .data(metadata.activator, $module);
            module.refresh();
            if (settings.hoverable) {
              module.bind.popup();
            }
          } else if (settings.popup) {
            $(settings.popup).data(metadata.activator, $module);
            module.verbose("Used popup specified in settings");
            module.refresh();
            if (settings.hoverable) {
              module.bind.popup();
            }
          } else {
            module.debug("No content specified skipping display", element);
          }
        },

        createID: function() {
          id = (Math.random().toString(16) + "000000000").substr(2, 8);
          elementNamespace = "." + id;
          module.verbose("Creating unique id for element", id);
        },

        // determines popup state
        toggle: function() {
          module.debug("Toggling pop-up");
          if (module.is.hidden()) {
            module.debug("Popup is hidden, showing pop-up");
            module.unbind.close();
            module.show();
          } else {
            module.debug("Popup is visible, hiding pop-up");
            module.hide();
          }
        },

        show: function(callback) {
          callback = callback || function() {};
          module.debug("Showing pop-up", settings.transition);
          if (
            module.is.hidden() &&
            !(module.is.active() && module.is.dropdown())
          ) {
            if (!module.exists()) {
              module.create();
            }
            if (settings.onShow.call($popup, element) === false) {
              module.debug(
                "onShow callback returned false, cancelling popup animation"
              );
              return;
            } else if (!settings.preserve && !settings.popup) {
              module.refresh();
            }
            if ($popup && module.set.position()) {
              module.save.conditions();
              if (settings.exclusive) {
                module.hideAll();
              }
              module.animate.show(callback);
            }
          }
        },

        hide: function(callback) {
          callback = callback || function() {};
          if (module.is.visible() || module.is.animating()) {
            if (settings.onHide.call($popup, element) === false) {
              module.debug(
                "onHide callback returned false, cancelling popup animation"
              );
              return;
            }
            module.remove.visible();
            module.unbind.close();
            module.restore.conditions();
            module.animate.hide(callback);
          }
        },

        hideAll: function() {
          $(selector.popup)
            .filter("." + className.popupVisible)
            .each(function() {
              $(this)
                .data(metadata.activator)
                .popup("hide");
            });
        },
        exists: function() {
          if (!$popup) {
            return false;
          }
          if (settings.inline || settings.popup) {
            return module.has.popup();
          } else {
            return $popup.closest($context).length >= 1 ? true : false;
          }
        },

        removePopup: function() {
          if (module.has.popup() && !settings.popup) {
            module.debug("Removing popup", $popup);
            $popup.remove();
            $popup = undefined;
            settings.onRemove.call($popup, element);
          }
        },

        save: {
          conditions: function() {
            module.cache = {
              title: $module.attr("title")
            };
            if (module.cache.title) {
              $module.removeAttr("title");
            }
            module.verbose("Saving original attributes", module.cache.title);
          }
        },
        restore: {
          conditions: function() {
            if (module.cache && module.cache.title) {
              $module.attr("title", module.cache.title);
              module.verbose(
                "Restoring original attributes",
                module.cache.title
              );
            }
            return true;
          }
        },
        supports: {
          svg: function() {
            return typeof SVGGraphicsElement === "undefined";
          }
        },
        animate: {
          show: function(callback) {
            callback = $.isFunction(callback) ? callback : function() {};
            if (
              settings.transition &&
              $.fn.transition !== undefined &&
              $module.transition("is supported")
            ) {
              module.set.visible();
              $popup.transition({
                animation: settings.transition + " in",
                queue: false,
                debug: settings.debug,
                verbose: settings.verbose,
                duration: settings.duration,
                onComplete: function() {
                  module.bind.close();
                  callback.call($popup, element);
                  settings.onVisible.call($popup, element);
                }
              });
            } else {
              module.error(error.noTransition);
            }
          },
          hide: function(callback) {
            callback = $.isFunction(callback) ? callback : function() {};
            module.debug("Hiding pop-up");
            if (settings.onHide.call($popup, element) === false) {
              module.debug(
                "onHide callback returned false, cancelling popup animation"
              );
              return;
            }
            if (
              settings.transition &&
              $.fn.transition !== undefined &&
              $module.transition("is supported")
            ) {
              $popup.transition({
                animation: settings.transition + " out",
                queue: false,
                duration: settings.duration,
                debug: settings.debug,
                verbose: settings.verbose,
                onComplete: function() {
                  module.reset();
                  callback.call($popup, element);
                  settings.onHidden.call($popup, element);
                }
              });
            } else {
              module.error(error.noTransition);
            }
          }
        },

        change: {
          content: function(html) {
            $popup.html(html);
          }
        },

        get: {
          html: function() {
            $module.removeData(metadata.html);
            return $module.data(metadata.html) || settings.html;
          },
          title: function() {
            $module.removeData(metadata.title);
            return $module.data(metadata.title) || settings.title;
          },
          content: function() {
            $module.removeData(metadata.content);
            return (
              $module.data(metadata.content) ||
              settings.content ||
              $module.attr("title")
            );
          },
          variation: function() {
            $module.removeData(metadata.variation);
            return $module.data(metadata.variation) || settings.variation;
          },
          popup: function() {
            return $popup;
          },
          popupOffset: function() {
            return $popup.offset();
          },
          calculations: function() {
            var $popupOffsetParent = module.get.offsetParent($popup),
              targetElement = $target[0],
              isWindow = $boundary[0] == window,
              targetPosition =
                settings.inline || (settings.popup && settings.movePopup)
                  ? $target.position()
                  : $target.offset(),
              screenPosition = isWindow
                ? { top: 0, left: 0 }
                : $boundary.offset(),
              calculations = {},
              scroll = isWindow
                ? { top: $window.scrollTop(), left: $window.scrollLeft() }
                : { top: 0, left: 0 },
              screen;
            calculations = {
              // element which is launching popup
              target: {
                element: $target[0],
                width: $target.outerWidth(),
                height: $target.outerHeight(),
                top: targetPosition.top,
                left: targetPosition.left,
                margin: {}
              },
              // popup itself
              popup: {
                width: $popup.outerWidth(),
                height: $popup.outerHeight()
              },
              // offset container (or 3d context)
              parent: {
                width: $offsetParent.outerWidth(),
                height: $offsetParent.outerHeight()
              },
              // screen boundaries
              screen: {
                top: screenPosition.top,
                left: screenPosition.left,
                scroll: {
                  top: scroll.top,
                  left: scroll.left
                },
                width: $boundary.width(),
                height: $boundary.height()
              }
            };

            // if popup offset context is not same as target, then adjust calculations
            if ($popupOffsetParent.get(0) !== $offsetParent.get(0)) {
              var popupOffset = $popupOffsetParent.offset();
              calculations.target.top -= popupOffset.top;
              calculations.target.left -= popupOffset.left;
              calculations.parent.width = $popupOffsetParent.outerWidth();
              calculations.parent.height = $popupOffsetParent.outerHeight();
            }

            // add in container calcs if fluid
            if (settings.setFluidWidth && module.is.fluid()) {
              calculations.container = {
                width: $popup.parent().outerWidth()
              };
              calculations.popup.width = calculations.container.width;
            }

            // add in margins if inline
            calculations.target.margin.top = settings.inline
              ? parseInt(
                  window
                    .getComputedStyle(targetElement)
                    .getPropertyValue("margin-top"),
                  10
                )
              : 0;
            calculations.target.margin.left = settings.inline
              ? module.is.rtl()
                ? parseInt(
                    window
                      .getComputedStyle(targetElement)
                      .getPropertyValue("margin-right"),
                    10
                  )
                : parseInt(
                    window
                      .getComputedStyle(targetElement)
                      .getPropertyValue("margin-left"),
                    10
                  )
              : 0;
            // calculate screen boundaries
            screen = calculations.screen;
            calculations.boundary = {
              top: screen.top + screen.scroll.top,
              bottom: screen.top + screen.scroll.top + screen.height,
              left: screen.left + screen.scroll.left,
              right: screen.left + screen.scroll.left + screen.width
            };
            return calculations;
          },
          id: function() {
            return id;
          },
          startEvent: function() {
            if (settings.on == "hover") {
              return "mouseenter";
            } else if (settings.on == "focus") {
              return "focus";
            }
            return false;
          },
          scrollEvent: function() {
            return "scroll";
          },
          endEvent: function() {
            if (settings.on == "hover") {
              return "mouseleave";
            } else if (settings.on == "focus") {
              return "blur";
            }
            return false;
          },
          distanceFromBoundary: function(offset, calculations) {
            var distanceFromBoundary = {},
              popup,
              boundary;
            calculations = calculations || module.get.calculations();

            // shorthand
            popup = calculations.popup;
            boundary = calculations.boundary;

            if (offset) {
              distanceFromBoundary = {
                top: offset.top - boundary.top,
                left: offset.left - boundary.left,
                right: boundary.right - (offset.left + popup.width),
                bottom: boundary.bottom - (offset.top + popup.height)
              };
              module.verbose(
                "Distance from boundaries determined",
                offset,
                distanceFromBoundary
              );
            }
            return distanceFromBoundary;
          },
          offsetParent: function($element) {
            var element = $element !== undefined ? $element[0] : $target[0],
              parentNode = element.parentNode,
              $node = $(parentNode);
            if (parentNode) {
              var is2D = $node.css("transform") === "none",
                isStatic = $node.css("position") === "static",
                isBody = $node.is("body");
              while (parentNode && !isBody && isStatic && is2D) {
                parentNode = parentNode.parentNode;
                $node = $(parentNode);
                is2D = $node.css("transform") === "none";
                isStatic = $node.css("position") === "static";
                isBody = $node.is("body");
              }
            }
            return $node && $node.length > 0 ? $node : $();
          },
          positions: function() {
            return {
              "top left": false,
              "top center": false,
              "top right": false,
              "bottom left": false,
              "bottom center": false,
              "bottom right": false,
              "left center": false,
              "right center": false
            };
          },
          nextPosition: function(position) {
            var positions = position.split(" "),
              verticalPosition = positions[0],
              horizontalPosition = positions[1],
              opposite = {
                top: "bottom",
                bottom: "top",
                left: "right",
                right: "left"
              },
              adjacent = {
                left: "center",
                center: "right",
                right: "left"
              },
              backup = {
                "top left": "top center",
                "top center": "top right",
                "top right": "right center",
                "right center": "bottom right",
                "bottom right": "bottom center",
                "bottom center": "bottom left",
                "bottom left": "left center",
                "left center": "top left"
              },
              adjacentsAvailable =
                verticalPosition == "top" || verticalPosition == "bottom",
              oppositeTried = false,
              adjacentTried = false,
              nextPosition = false;
            if (!triedPositions) {
              module.verbose("All available positions available");
              triedPositions = module.get.positions();
            }

            module.debug("Recording last position tried", position);
            triedPositions[position] = true;

            if (settings.prefer === "opposite") {
              nextPosition = [opposite[verticalPosition], horizontalPosition];
              nextPosition = nextPosition.join(" ");
              oppositeTried = triedPositions[nextPosition] === true;
              module.debug("Trying opposite strategy", nextPosition);
            }
            if (settings.prefer === "adjacent" && adjacentsAvailable) {
              nextPosition = [verticalPosition, adjacent[horizontalPosition]];
              nextPosition = nextPosition.join(" ");
              adjacentTried = triedPositions[nextPosition] === true;
              module.debug("Trying adjacent strategy", nextPosition);
            }
            if (adjacentTried || oppositeTried) {
              module.debug("Using backup position", nextPosition);
              nextPosition = backup[position];
            }
            return nextPosition;
          }
        },

        set: {
          position: function(position, calculations) {
            // exit conditions
            if ($target.length === 0 || $popup.length === 0) {
              module.error(error.notFound);
              return;
            }
            var offset,
              distanceAway,
              target,
              popup,
              parent,
              positioning,
              popupOffset,
              distanceFromBoundary;

            calculations = calculations || module.get.calculations();
            position =
              position || $module.data(metadata.position) || settings.position;

            offset = $module.data(metadata.offset) || settings.offset;
            distanceAway = settings.distanceAway;

            // shorthand
            target = calculations.target;
            popup = calculations.popup;
            parent = calculations.parent;

            if (module.should.centerArrow(calculations)) {
              module.verbose(
                "Adjusting offset to center arrow on small target element"
              );
              if (position == "top left" || position == "bottom left") {
                offset += target.width / 2;
                offset -= settings.arrowPixelsFromEdge;
              }
              if (position == "top right" || position == "bottom right") {
                offset -= target.width / 2;
                offset += settings.arrowPixelsFromEdge;
              }
            }

            if (
              target.width === 0 &&
              target.height === 0 &&
              !module.is.svg(target.element)
            ) {
              module.debug("Popup target is hidden, no action taken");
              return false;
            }

            if (settings.inline) {
              module.debug("Adding margin to calculation", target.margin);
              if (position == "left center" || position == "right center") {
                offset += target.margin.top;
                distanceAway += -target.margin.left;
              } else if (
                position == "top left" ||
                position == "top center" ||
                position == "top right"
              ) {
                offset += target.margin.left;
                distanceAway -= target.margin.top;
              } else {
                offset += target.margin.left;
                distanceAway += target.margin.top;
              }
            }

            module.debug(
              "Determining popup position from calculations",
              position,
              calculations
            );

            if (module.is.rtl()) {
              position = position.replace(/left|right/g, function(match) {
                return match == "left" ? "right" : "left";
              });
              module.debug("RTL: Popup position updated", position);
            }

            // if last attempt use specified last resort position
            if (
              searchDepth == settings.maxSearchDepth &&
              typeof settings.lastResort === "string"
            ) {
              position = settings.lastResort;
            }

            switch (position) {
              case "top left":
                positioning = {
                  top: "auto",
                  bottom: parent.height - target.top + distanceAway,
                  left: target.left + offset,
                  right: "auto"
                };
                break;
              case "top center":
                positioning = {
                  bottom: parent.height - target.top + distanceAway,
                  left:
                    target.left + target.width / 2 - popup.width / 2 + offset,
                  top: "auto",
                  right: "auto"
                };
                break;
              case "top right":
                positioning = {
                  bottom: parent.height - target.top + distanceAway,
                  right: parent.width - target.left - target.width - offset,
                  top: "auto",
                  left: "auto"
                };
                break;
              case "left center":
                positioning = {
                  top:
                    target.top + target.height / 2 - popup.height / 2 + offset,
                  right: parent.width - target.left + distanceAway,
                  left: "auto",
                  bottom: "auto"
                };
                break;
              case "right center":
                positioning = {
                  top:
                    target.top + target.height / 2 - popup.height / 2 + offset,
                  left: target.left + target.width + distanceAway,
                  bottom: "auto",
                  right: "auto"
                };
                break;
              case "bottom left":
                positioning = {
                  top: target.top + target.height + distanceAway,
                  left: target.left + offset,
                  bottom: "auto",
                  right: "auto"
                };
                break;
              case "bottom center":
                positioning = {
                  top: target.top + target.height + distanceAway,
                  left:
                    target.left + target.width / 2 - popup.width / 2 + offset,
                  bottom: "auto",
                  right: "auto"
                };
                break;
              case "bottom right":
                positioning = {
                  top: target.top + target.height + distanceAway,
                  right: parent.width - target.left - target.width - offset,
                  left: "auto",
                  bottom: "auto"
                };
                break;
            }
            if (positioning === undefined) {
              module.error(error.invalidPosition, position);
            }

            module.debug("Calculated popup positioning values", positioning);

            // tentatively place on stage
            $popup
              .css(positioning)
              .removeClass(className.position)
              .addClass(position)
              .addClass(className.loading);

            popupOffset = module.get.popupOffset();

            // see if any boundaries are surpassed with this tentative position
            distanceFromBoundary = module.get.distanceFromBoundary(
              popupOffset,
              calculations
            );

            if (module.is.offstage(distanceFromBoundary, position)) {
              module.debug("Position is outside viewport", position);
              if (searchDepth < settings.maxSearchDepth) {
                searchDepth++;
                position = module.get.nextPosition(position);
                module.debug("Trying new position", position);
                return $popup
                  ? module.set.position(position, calculations)
                  : false;
              } else {
                if (settings.lastResort) {
                  module.debug("No position found, showing with last position");
                } else {
                  module.debug(
                    "Popup could not find a position to display",
                    $popup
                  );
                  module.error(error.cannotPlace, element);
                  module.remove.attempts();
                  module.remove.loading();
                  module.reset();
                  settings.onUnplaceable.call($popup, element);
                  return false;
                }
              }
            }
            module.debug("Position is on stage", position);
            module.remove.attempts();
            module.remove.loading();
            if (settings.setFluidWidth && module.is.fluid()) {
              module.set.fluidWidth(calculations);
            }
            return true;
          },

          fluidWidth: function(calculations) {
            calculations = calculations || module.get.calculations();
            module.debug(
              "Automatically setting element width to parent width",
              calculations.parent.width
            );
            $popup.css("width", calculations.container.width);
          },

          variation: function(variation) {
            variation = variation || module.get.variation();
            if (variation && module.has.popup()) {
              module.verbose("Adding variation to popup", variation);
              $popup.addClass(variation);
            }
          },

          visible: function() {
            $module.addClass(className.visible);
          }
        },

        remove: {
          loading: function() {
            $popup.removeClass(className.loading);
          },
          variation: function(variation) {
            variation = variation || module.get.variation();
            if (variation) {
              module.verbose("Removing variation", variation);
              $popup.removeClass(variation);
            }
          },
          visible: function() {
            $module.removeClass(className.visible);
          },
          attempts: function() {
            module.verbose("Resetting all searched positions");
            searchDepth = 0;
            triedPositions = false;
          }
        },

        bind: {
          events: function() {
            module.debug("Binding popup events to module");
            if (settings.on == "click") {
              $module.on("click" + eventNamespace, module.toggle);
            }
            if (settings.on == "hover" && hasTouch) {
              $module.on(
                "touchstart" + eventNamespace,
                module.event.touchstart
              );
            }
            if (module.get.startEvent()) {
              $module
                .on(
                  module.get.startEvent() + eventNamespace,
                  module.event.start
                )
                .on(module.get.endEvent() + eventNamespace, module.event.end);
            }
            if (settings.target) {
              module.debug("Target set to element", $target);
            }
            $window.on("resize" + elementNamespace, module.event.resize);
          },
          popup: function() {
            module.verbose("Allowing hover events on popup to prevent closing");
            if ($popup && module.has.popup()) {
              $popup
                .on("mouseenter" + eventNamespace, module.event.start)
                .on("mouseleave" + eventNamespace, module.event.end);
            }
          },
          close: function() {
            if (
              settings.hideOnScroll === true ||
              (settings.hideOnScroll == "auto" && settings.on != "click")
            ) {
              module.bind.closeOnScroll();
            }
            if (settings.on == "hover" && openedWithTouch) {
              module.bind.touchClose();
            }
            if (settings.on == "click" && settings.closable) {
              module.bind.clickaway();
            }
          },
          closeOnScroll: function() {
            module.verbose("Binding scroll close event to document");
            $scrollContext.one(
              module.get.scrollEvent() + elementNamespace,
              module.event.hideGracefully
            );
          },
          touchClose: function() {
            module.verbose("Binding popup touchclose event to document");
            $document.on("touchstart" + elementNamespace, function(event) {
              module.verbose("Touched away from popup");
              module.event.hideGracefully.call(element, event);
            });
          },
          clickaway: function() {
            module.verbose("Binding popup close event to document");
            $document.on("click" + elementNamespace, function(event) {
              module.verbose("Clicked away from popup");
              module.event.hideGracefully.call(element, event);
            });
          }
        },

        unbind: {
          events: function() {
            $window.off(elementNamespace);
            $module.off(eventNamespace);
          },
          close: function() {
            $document.off(elementNamespace);
            $scrollContext.off(elementNamespace);
          }
        },

        has: {
          popup: function() {
            return $popup && $popup.length > 0;
          }
        },

        should: {
          centerArrow: function(calculations) {
            return (
              !module.is.basic() &&
              calculations.target.width <= settings.arrowPixelsFromEdge * 2
            );
          }
        },

        is: {
          offstage: function(distanceFromBoundary, position) {
            var offstage = [];
            // return boundaries that have been surpassed
            $.each(distanceFromBoundary, function(direction, distance) {
              if (distance < -settings.jitter) {
                module.debug(
                  "Position exceeds allowable distance from edge",
                  direction,
                  distance,
                  position
                );
                offstage.push(direction);
              }
            });
            if (offstage.length > 0) {
              return true;
            } else {
              return false;
            }
          },
          svg: function(element) {
            return (
              module.supports.svg() && element instanceof SVGGraphicsElement
            );
          },
          basic: function() {
            return $module.hasClass(className.basic);
          },
          active: function() {
            return $module.hasClass(className.active);
          },
          animating: function() {
            return $popup !== undefined && $popup.hasClass(className.animating);
          },
          fluid: function() {
            return $popup !== undefined && $popup.hasClass(className.fluid);
          },
          visible: function() {
            return (
              $popup !== undefined && $popup.hasClass(className.popupVisible)
            );
          },
          dropdown: function() {
            return $module.hasClass(className.dropdown);
          },
          hidden: function() {
            return !module.is.visible();
          },
          rtl: function() {
            return $module.css("direction") == "rtl";
          }
        },

        reset: function() {
          module.remove.visible();
          if (settings.preserve) {
            if ($.fn.transition !== undefined) {
              $popup.transition("remove transition");
            }
          } else {
            module.removePopup();
          }
        },

        setting: function(name, value) {
          if ($.isPlainObject(name)) {
            $.extend(true, settings, name);
          } else if (value !== undefined) {
            settings[name] = value;
          } else {
            return settings[name];
          }
        },
        internal: function(name, value) {
          if ($.isPlainObject(name)) {
            $.extend(true, module, name);
          } else if (value !== undefined) {
            module[name] = value;
          } else {
            return module[name];
          }
        },
        debug: function() {
          if (!settings.silent && settings.debug) {
            if (settings.performance) {
              module.performance.log(arguments);
            } else {
              module.debug = Function.prototype.bind.call(
                console.info,
                console,
                settings.name + ":"
              );
              module.debug.apply(console, arguments);
            }
          }
        },
        verbose: function() {
          if (!settings.silent && settings.verbose && settings.debug) {
            if (settings.performance) {
              module.performance.log(arguments);
            } else {
              module.verbose = Function.prototype.bind.call(
                console.info,
                console,
                settings.name + ":"
              );
              module.verbose.apply(console, arguments);
            }
          }
        },
        error: function() {
          if (!settings.silent) {
            module.error = Function.prototype.bind.call(
              console.error,
              console,
              settings.name + ":"
            );
            module.error.apply(console, arguments);
          }
        },
        performance: {
          log: function(message) {
            var currentTime, executionTime, previousTime;
            if (settings.performance) {
              currentTime = new Date().getTime();
              previousTime = time || currentTime;
              executionTime = currentTime - previousTime;
              time = currentTime;
              performance.push({
                Name: message[0],
                Arguments: [].slice.call(message, 1) || "",
                Element: element,
                "Execution Time": executionTime
              });
            }
            clearTimeout(module.performance.timer);
            module.performance.timer = setTimeout(
              module.performance.display,
              500
            );
          },
          display: function() {
            var title = settings.name + ":",
              totalTime = 0;
            time = false;
            clearTimeout(module.performance.timer);
            $.each(performance, function(index, data) {
              totalTime += data["Execution Time"];
            });
            title += " " + totalTime + "ms";
            if (moduleSelector) {
              title += " '" + moduleSelector + "'";
            }
            if (
              (console.group !== undefined || console.table !== undefined) &&
              performance.length > 0
            ) {
              console.groupCollapsed(title);
              if (console.table) {
                console.table(performance);
              } else {
                $.each(performance, function(index, data) {
                  console.log(
                    data["Name"] + ": " + data["Execution Time"] + "ms"
                  );
                });
              }
              console.groupEnd();
            }
            performance = [];
          }
        },
        invoke: function(query, passedArguments, context) {
          var object = instance,
            maxDepth,
            found,
            response;
          passedArguments = passedArguments || queryArguments;
          context = element || context;
          if (typeof query == "string" && object !== undefined) {
            query = query.split(/[\. ]/);
            maxDepth = query.length - 1;
            $.each(query, function(depth, value) {
              var camelCaseValue =
                depth != maxDepth
                  ? value +
                    query[depth + 1].charAt(0).toUpperCase() +
                    query[depth + 1].slice(1)
                  : query;
              if (
                $.isPlainObject(object[camelCaseValue]) &&
                depth != maxDepth
              ) {
                object = object[camelCaseValue];
              } else if (object[camelCaseValue] !== undefined) {
                found = object[camelCaseValue];
                return false;
              } else if ($.isPlainObject(object[value]) && depth != maxDepth) {
                object = object[value];
              } else if (object[value] !== undefined) {
                found = object[value];
                return false;
              } else {
                return false;
              }
            });
          }
          if ($.isFunction(found)) {
            response = found.apply(context, passedArguments);
          } else if (found !== undefined) {
            response = found;
          }
          if ($.isArray(returnedValue)) {
            returnedValue.push(response);
          } else if (returnedValue !== undefined) {
            returnedValue = [returnedValue, response];
          } else if (response !== undefined) {
            returnedValue = response;
          }
          return found;
        }
      };

      if (methodInvoked) {
        if (instance === undefined) {
          module.initialize();
        }
        module.invoke(query);
      } else {
        if (instance !== undefined) {
          instance.invoke("destroy");
        }
        module.initialize();
      }
    });

    return returnedValue !== undefined ? returnedValue : this;
  };

  $.fn.popup.settings = {
    name: "Popup",

    // module settings
    silent: false,
    debug: false,
    verbose: false,
    performance: true,
    namespace: "popup",

    // whether it should use dom mutation observers
    observeChanges: true,

    // callback only when element added to dom
    onCreate: function() {},

    // callback before element removed from dom
    onRemove: function() {},

    // callback before show animation
    onShow: function() {},

    // callback after show animation
    onVisible: function() {},

    // callback before hide animation
    onHide: function() {},

    // callback when popup cannot be positioned in visible screen
    onUnplaceable: function() {},

    // callback after hide animation
    onHidden: function() {},

    // when to show popup
    on: "hover",

    // element to use to determine if popup is out of boundary
    boundary: window,

    // whether to add touchstart events when using hover
    addTouchEvents: true,

    // default position relative to element
    position: "top left",

    // name of variation to use
    variation: "",

    // whether popup should be moved to context
    movePopup: true,

    // element which popup should be relative to
    target: false,

    // jq selector or element that should be used as popup
    popup: false,

    // popup should remain inline next to activator
    inline: false,

    // popup should be removed from page on hide
    preserve: false,

    // popup should not close when being hovered on
    hoverable: false,

    // explicitly set content
    content: false,

    // explicitly set html
    html: false,

    // explicitly set title
    title: false,

    // whether automatically close on clickaway when on click
    closable: true,

    // automatically hide on scroll
    hideOnScroll: "auto",

    // hide other popups on show
    exclusive: false,

    // context to attach popups
    context: "body",

    // context for binding scroll events
    scrollContext: window,

    // position to prefer when calculating new position
    prefer: "opposite",

    // specify position to appear even if it doesn't fit
    lastResort: false,

    // number of pixels from edge of popup to pointing arrow center (used from centering)
    arrowPixelsFromEdge: 20,

    // delay used to prevent accidental refiring of animations due to user error
    delay: {
      show: 50,
      hide: 70
    },

    // whether fluid variation should assign width explicitly
    setFluidWidth: true,

    // transition settings
    duration: 200,
    transition: "scale",

    // distance away from activating element in px
    distanceAway: 0,

    // number of pixels an element is allowed to be "offstage" for a position to be chosen (allows for rounding)
    jitter: 2,

    // offset on aligning axis from calculated position
    offset: 0,

    // maximum times to look for a position before failing (9 positions total)
    maxSearchDepth: 15,

    error: {
      invalidPosition: "The position you specified is not a valid position",
      cannotPlace: "Popup does not fit within the boundaries of the viewport",
      method: "The method you called is not defined.",
      noTransition:
        "This module requires ui transitions <https://github.com/Semantic-Org/UI-Transition>",
      notFound: "The target or popup you specified does not exist on the page"
    },

    metadata: {
      activator: "activator",
      content: "content",
      html: "html",
      offset: "offset",
      position: "position",
      title: "title",
      variation: "variation"
    },

    className: {
      active: "active",
      basic: "basic",
      animating: "animating",
      dropdown: "dropdown",
      fluid: "fluid",
      loading: "loading",
      popup: "ui popup",
      position: "top left center bottom right",
      visible: "visible",
      popupVisible: "visible"
    },

    selector: {
      popup: ".ui.popup"
    },

    templates: {
      escape: function(string) {
        var badChars = /[&<>"'`]/g,
          shouldEscape = /[&<>"'`]/,
          escape = {
            "&": "&amp;",
            "<": "&lt;",
            ">": "&gt;",
            '"': "&quot;",
            "'": "&#x27;",
            "`": "&#x60;"
          },
          escapedChar = function(chr) {
            return escape[chr];
          };
        if (shouldEscape.test(string)) {
          return string.replace(badChars, escapedChar);
        }
        return string;
      },
      popup: function(text) {
        var html = "",
          escape = $.fn.popup.settings.templates.escape;
        if (typeof text !== undefined) {
          if (typeof text.title !== undefined && text.title) {
            text.title = escape(text.title);
            html += '<div class="header">' + text.title + "</div>";
          }
          if (typeof text.content !== undefined && text.content) {
            text.content = escape(text.content);
            html += '<div class="content">' + text.content + "</div>";
          }
        }
        return html;
      }
    }
  };
})(jQuery, window, document);
